/*
Copyright 2021 LuJun

Permission is hereby granted, free of charge, to any person obtaining a copy 
of this software and associated documentation files (the "Software"), to deal 
in the Software without restriction, including without limitation the rights 
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
copies of the Software, and to permit persons to whom the Software is furnished 
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in 
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

/// <reference path="../../log/log-manager.ts" />
/// <reference path="../../utils/js-buffer.ts" />

namespace lcc {

let net = require("net");

const TAG = "lcc/network/socket/socket-line.ts";

/**
 * @zh
 * SOCKET心跳时间
 */
export let SOCKET_HEARTTICK = 10;

/**
 * @zh
 * SOCKET初始缓冲区大小
 */
export let SOCKET_BUFFERSIZE = 512;

/**
 * @zh
 * SOCKET配置
 */
export interface SocketConfig {
	/**
     * @zh
	 * 地址<br/>
     * 服务器，客户端
	 */
	address?:string;

    /**
     * @zh
     * 端口<br/>
     * 服务器，客户端
     */
	port?:number
	
    /**
     * @zh
     * 连接实体
     * 
     * @internal
     */
    entity?:any;
}

/**
 * @zh
 * SOCKET网络线
 */
export class SocketLine extends NetLine {
    
	/**
     * @zh
	 * 网络线配置
	 */
	private _config:SocketConfig = null;
	
	/**
     * @zh
	 * 网络线实体
	 */
	private _entity:any = null;
	
	/**
     * @zh
	 * 就绪
	 */
	private _ready:boolean = false;
	
	/**
     * @zh
	 * 心跳时间限制
	 */
	private _heartTimeLimit:number = SOCKET_HEARTTICK;

	/**
     * @zh
	 * 检查心跳时间
	 */
	private _checkHeartTime:number = 0;

	/**
     * @zh
	 * 发送心跳时间<br/>
     * 客户端
	 */
    private _sendHeartTime:number = 0;
    
    /**
     * @zh
     * 流缓冲读取器
     */
    private _streamReader:js.StreamReader = new js.StreamReader(SOCKET_BUFFERSIZE);

	/**
     * @zh
	 * 获得地址
	 */
	public getAddress(){
		return this._config.address;
	}

	/**
     * @zh
	 * 获得端口
	 */
	public getPort(){
		return this._config.port;
	}

	/**
     * @zh
	 * 配置网络线
     * 
     * @param config 配置对象
	 */
	public config(config:SocketConfig){
		DEBUG.NETWORK && logMgr.debug(TAG, `${this.getName()} config`);
		let parent = this.getParent();
		if(parent){
			this.useProtoBuf(parent.isUseProtoBuf());
		}
        this._config = config;
        if(this._mode == NetLineMode.CONNECT){
            this._entity = config.entity;
            this.setupConnect();
        }
    }
    
	/**
     * @zh
	 * 检查网络线是否有效
	 */
	public isValid() {
		return this._entity && this._ready;
	}

    /**
     * @zh
     * 处理网络线数据
     * 
     * @param netData 网络数据
     */
    private onDealNetData(netData:NetData){
		this._checkHeartTime = 0;
		DEBUG.NETWORK && logMgr.debug(TAG, `${this.getName()} onDealNetData ${JSON.stringify(netData)}`);
        if(netData.protocols){
            this.pushDispatchCaches(netData.protocols);
        }
        if(netData.type == netdata.NetDataType.SYNC_HERTTICK_TIME){
            if(this._mode === NetLineMode.CLIENT){
                this._heartTimeLimit = (netData.data as netdata.SyncHeartTickTime).heartTime;
                this.writeNetLineData(netdata.NetDataType.HERT_TICK);
				this._sendHeartTime = 0;
				DEBUG.NETWORK && logMgr.debug(TAG, `${this.getName()} SYNC_HERTTICK_TIME ${this._heartTimeLimit}`);
            }
        }else if(netData.type == netdata.NetDataType.HERT_TICK){
			DEBUG.NETWORK && logMgr.debug(TAG, `${this.getName()} HERT_TICK`);
            if(this._mode === NetLineMode.CONNECT){
				DEBUG.NETWORK && logMgr.debug(TAG, `${this.getName()} CONNECT HERT_TICK`);
                this.writeNetLineData(netdata.NetDataType.HERT_TICK);
            }
        }
    }

    /**
     * @zh
     * 当读取网络数据
     */
    private onReadNetData(){
        DEBUG.NETWORK && logMgr.debug(TAG, `${this.getName()} StreamReader ${this._streamReader.getRemainSize()}`);
		if(this.isValid()){
			while(true){
				let remainSize = this._streamReader.getRemainSize();
				if(remainSize < 2){
					break;
				}
				let dataSize = this._streamReader.peekULong();
				if(remainSize < dataSize){
					break;
				}
				dataSize = this._streamReader.readULong();
				let data = this._streamReader.readData(dataSize);
				DEBUG.NETWORK && logMgr.debug(TAG, `${this.getName()} onReadNetData |${data}|`);
				if(data){
					let netData:NetData = unpackNetData(data)
					if(netData){
						this.onDealNetData(netData);
					}
				}
			}
		}
    }
    
    /**
     * @zh
     * 安装连接
     */
    private setupConnect(){
		DEBUG.NETWORK && logMgr.debug(TAG, `${this.getName()} setupConnect`);
        this._checkHeartTime = 0;
        this._config.address = this._entity.address().address;
        this._ready = true;
        this._entity.on("close", (code:number, reason:string)=>{
			DEBUG.NETWORK && logMgr.debug(TAG, `${this.getName()} CLIENT close`);
            if(this._entity){
                this._entity = null;
                this._ready = false;
                netServer._removeConnectLine(this._name);
                try{
                    this.emit("onClose", this, code);
                }catch(e){
                    logMgr.debug(TAG, `${this.getName()} setupConnect close Exception : ${e}`);
                }
            }
        });
        this._entity.on("error", (error:any)=>{
			DEBUG.NETWORK && logMgr.debug(TAG, `${this.getName()} CLIENT error ${error}`);
            try{
                this.emit("onError", this, error);
            }catch(e){
                logMgr.debug(TAG, `${this.getName()} setupConnect error Exception : ${e}`);
            }
            this.close();
        });
        this._entity.on("data", (data:Buffer)=>{
            DEBUG.NETWORK && logMgr.debug(TAG, `${this.getName()} CLIENT data ${JSON.stringify(data.toJSON())}`);
            this._streamReader.append(data);
            this.onReadNetData();
        });
        this.writeNetLineData<netdata.SyncHeartTickTime>(netdata.NetDataType.SYNC_HERTTICK_TIME, {
            heartTime : this._heartTimeLimit,
        });
        DEBUG.NETWORK && logMgr.debug(TAG, `${this.getName()} SyncHeartTickTime ${this._heartTimeLimit}`);
    }

	/**
     * @zh
	 * 打开网络线
	 */
    public async open():Promise<boolean>{
		if(!this._entity){
			if(this._mode === NetLineMode.CLIENT){
				DEBUG.NETWORK && logMgr.debug(TAG, `${this.getName()} CLIENT open ${this._config.port} ${this._config.address}`);
                this._entity = net.connect(this._config.port, this._config.address);
                this._entity.on("connect",()=>{
					DEBUG.NETWORK && logMgr.debug(TAG, `${this.getName()} CLIENT connect`);
                    this._checkHeartTime = 0;
                    this._sendHeartTime = 0;
					this._ready = true;
                    try{
                        this.emit("onOpen", true, this);
                    }catch(e){
                        logMgr.debug(TAG, `${this.getName()} CLIENT open connect Exception : ${e}`);
                    }
                });
                this._entity.on("close",(code:number, reason:string)=>{
					DEBUG.NETWORK && logMgr.debug(TAG, `${this.getName()} CLIENT close`);
                    if(this._entity){
						this._entity = null;
                        this._ready = false;
                        try{
                            this.emit("onClose", this, code);
                        }catch(e){
                            logMgr.debug(TAG, `${this.getName()} CLIENT open close Exception : ${e}`);
                        }
					}
                });
                this._entity.on("error",(error:string)=>{
					DEBUG.NETWORK && logMgr.debug(TAG, `${this.getName()} CLIENT error ${error}`);
                    try{
                        if(this._ready){
                            this.emit("onError", this, error);
                        }else{
                            this.emit("onOpen", false, this);
                        }
                    }catch(e){
                        logMgr.debug(TAG, `${this.getName()} CLIENT open error Exception : ${e}`);
                    }
                    this.close();
                });
                this._entity.on("data",(data:Buffer)=>{
                    DEBUG.NETWORK && logMgr.debug(TAG, `${this.getName()} CLIENT data ${JSON.stringify(data.toJSON())}`);
                    this._streamReader.append(data);
                    this.onReadNetData();
                });
			}else if(this._mode === NetLineMode.SERVER){
				DEBUG.NETWORK && logMgr.debug(TAG, `${this.getName()} SERVER open ${this._config.port} ${this._config.address}`);
                this._entity = net.createServer();
                this._entity.on("listening",()=>{
					DEBUG.NETWORK && logMgr.debug(TAG, `${this.getName()} SERVER listening`);
                    this._ready = true;
                    try{
                        this.emit("onOpen", true, this);
                    }catch(e){
                        logMgr.debug(TAG, `${this.getName()} SERVER open listening Exception : ${e}`);
                    }
                });
                this._entity.on("connection",(newcon:any)=>{
					DEBUG.NETWORK && logMgr.debug(TAG, `${this.getName()} SERVER connection`);
                    let netline = new SocketLine(`${NetLineType.SOCKET}|${netServer._newConnectName()}`, NetLineMode.CONNECT, NetLineType.SOCKET, this);
                    netline.config({ entity : newcon });
                    netServer._addConnectLine(netline);
                    try{
                        this.emit("newConnect", netline);
                    }catch(e){
                        logMgr.debug(TAG, `${this.getName()} SERVER open connection Exception : ${e}`);
                    }
                });
                this._entity.on("close",()=>{
					DEBUG.NETWORK && logMgr.debug(TAG, `${this.getName()} SERVER close`);
                    if(this._entity){
						this._entity = null;
                        this._ready = false;
                        try{
                            this.emit("onClose", this, 0);
                        }catch(e){
                            logMgr.debug(TAG, `${this.getName()} SERVER open close Exception : ${e}`);
                        }
					}
                });
                this._entity.on("error",(error:any)=>{
					DEBUG.NETWORK && logMgr.debug(TAG, `${this.getName()} SERVER error ${error}`);
                    try{
                        if(this._ready){
                            this.emit("onError", this, error);
                        }else{
                            this.emit("onOpen", false, this);
                        }
                    }catch(e){
                        logMgr.debug(TAG, `${this.getName()} SERVER open error Exception : ${e}`);
                    }
					this.close();
                });
                this._entity.listen(this._config.port, this._config.address);
            }
		}
		if(this._entity){
			return new Promise((resolve)=>{ 
				if(this._ready){
					resolve(true);
				}else{
					this.once(`onOpen`, resolve); 
				}
			});
		}
	}
    
	/**
     * @zh
	 * 关闭网络线
	 */
	public close(){
		if(this._entity){
			DEBUG.NETWORK && logMgr.debug(TAG, `${this.getName()} ${this._mode} close`);
			let ent = this._entity;
			this._entity = null;
			if(this._ready){
                if(this._mode === NetLineMode.SERVER){
                    ent.close();
                }else{
                    ent.end();
                }
			}
            this._ready = false;
            try{
                this.emit("onClose", this, 0);
            }catch(e){
                logMgr.debug(TAG, `${this.getName()} close Exception : ${e}`);
            }
            if(this._mode === NetLineMode.SERVER){
                netServer._removeConnectLines(this);
            }else if(this._mode === NetLineMode.CONNECT){
                netServer._removeConnectLine(this._name);
            }
		}
	}
	
	/**
     * @zh
	 * 更新网络线
     * 
	 * @param dt - 间隔时间
	 */
	public onUpdate(dt:number){
        if(this._mode !== NetLineMode.SERVER){
            if(this._entity && this._ready){
                this._checkHeartTime += dt;
                if(this._checkHeartTime >= this._heartTimeLimit){
					DEBUG.NETWORK && logMgr.debug(TAG, `${this.getName()} onUpdate checkHeartTime close`);
                    this.close();
                }
                if(this._mode === NetLineMode.CLIENT){
                    this._sendHeartTime += dt;
                    if(this._sendHeartTime >= this._heartTimeLimit / 2){
						DEBUG.NETWORK && logMgr.debug(TAG, `${this.getName()} onUpdate sendHeartTime`);
                        this.writeNetLineData(netdata.NetDataType.HERT_TICK);
						this._sendHeartTime = 0;
                    }
                }
            }
        }
	}
    
	/**
     * @zh
	 * 发送网络线数据
     * 
	 * @param data - 网络数据
	 */
	protected sendNetData(data:NetData){
		if(this._entity && this._ready){
            let buffer = packNetData(data, this.isUseProtoBuf());
            let wBuff = new js.BufferWriter();
            wBuff.writeULong(buffer.byteLength);
            wBuff.writeData(buffer)
			this._entity.write(wBuff.getData());
		}else{
			console.warn(TAG, `${this.getName()} SocketLine ${this._name} sendNetData fail!`);
		}
	}
}

//注册网络类型
NetLine.registerNetType(NetLineType.SOCKET, SocketLine);

}
